package com.task.scheduling.job;

import com.task.scheduling.constants.Constants;
import com.task.scheduling.dao.TaskSchedulingDao;
import com.task.scheduling.model.TaskSchedulingModel;
import com.task.scheduling.enums.TaskStatus;
import com.task.scheduling.util.HostUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * @program: task-scheduling
 * @description: 检测可能死掉的任务
 * @author: zhouliangliang
 * @create: 2018-11-14 14:33
 *
 *
 * 1.任务执行时将任务id放入全局唯一map中
 * 2.定时任务每隔一分钟更新map中任务的心跳
 * 3.检测任务每隔10分钟检测一次心跳数量,如果心跳数量达不到(当前时间 减去 任务开始执行时间的分钟数)的60%
 *   那么判定任务有可能死掉，如果任务被判定为有可能死掉，那么每隔一分钟检测一次心跳数量，连续五次检测心跳数量不变
 *   则任务死掉，复活任务
 *
 * 检测程序检测
 **/
public class CheckDeadJob implements Runnable{

    private Logger log = LoggerFactory.getLogger(CheckDeadJob.class);
    private TaskSchedulingDao taskSchedulingDao;

    public CheckDeadJob(TaskSchedulingDao taskSchedulingDao) {
        this.taskSchedulingDao = taskSchedulingDao;
    }

    @Override
    public void run() {

        log.info("检测任务执行");

        //睡眠随机时间，以防止多台机器争抢任务
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
        } catch (Exception e) {

        }

        while(true) {
            try {
                Thread.sleep(Constants.checkDeathInterval);
                List<TaskSchedulingModel> modelList = taskSchedulingDao.getDeathTask(new Date());

                List<DeadTask> deadList = new CopyOnWriteArrayList<>();

                if(modelList != null && modelList.size() != 0) {
                    log.info("检测到 {} 个可能死亡任务", modelList.size());
                    //锁定监控任务
                    for (TaskSchedulingModel model : modelList) {

                        //任务状态更新为监控状态
                        int num = taskSchedulingDao.updateMonitor(model.getId(), 0, 1);

                        //如果更新成功，纳入监控
                        if(num == 1) {
                            DeadTask dt = new DeadTask();

                            dt.setId(model.getId());
                            dt.setTimeCount(model.getTimeCount());

                            deadList.add(dt);

                            log.info("任务 {} 被 {} 机器监控!", model.getId(), HostUtil.getIp());
                        }
                    }

                    if(deadList.size() > 0) {
                        //执行监控
                        for(int i=1; i<=Constants.deathTaskCheckTimes; i++) {
                            Thread.sleep(Constants.heartBeatInterval);
                            for(DeadTask d : deadList) {
                                TaskSchedulingModel checkModel = taskSchedulingDao.getTaskById(d.getId());

                                //执行成功或者是计数器改变，则任务没死
                                if(checkModel.getTaskStatus() == TaskStatus.SUCCESS.getCode()
                                        || checkModel.getTimeCount().intValue() != d.getTimeCount().intValue()) {
                                    log.info("任务存活, 任务 {} 的状态为 {}, checkModel.timeCount {}, d.timeCount {}, timeCount是否相等 : "
                                            + (checkModel.getTimeCount() == d.getTimeCount()), d.getId(), checkModel.getTaskStatus(), checkModel.getTimeCount(), d.getTimeCount());
                                    //程序判定没有死掉的，修改监控状态
                                    taskSchedulingDao.updateMonitor(checkModel.getId(), 1,0);
                                    deadList.remove(d);
                                } else {
                                    log.info("任务 {} 监控 {} 次, 未通过监控!", checkModel.getId(), i);
                                }

                            }
                            //没有需要监控的数据则跳出循环
                            if(deadList.size() == 0) {
                                break;
                            }
                        }
                    }


                    //五次监控之后复活任务
                    for(DeadTask d : deadList) {
                        taskSchedulingDao.reviveTaskById(d.getId(), new Date());
                        log.info("任务 {} 通过监控复活, 等待调度执行!", d.getId());
                    }
                } else {
                    log.info("监控执行, 未获得可能死亡任务!");
                }

            } catch (Exception e) {
                log.error("CheckDeadJob error" + e.getMessage(), e);
            }
        }
    }

    class DeadTask{

        private Long id;
        private Integer timeCount;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Integer getTimeCount() {
            return timeCount;
        }

        public void setTimeCount(Integer timeCount) {
            this.timeCount = timeCount;
        }
    }
}
